package main

import (
	"errors"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/dsoprea/go-exif/v3"
	exifcommon "github.com/dsoprea/go-exif/v3/common"
)

type ExifReader struct {
	imagePath string
	exifTags  []exif.ExifTag
	location  *time.Location
}

func NewExifReader(imagePath string) *ExifReader {
	return &ExifReader{imagePath: imagePath}
}

func (r *ExifReader) readExifTagsFromImage(imagePath string) error {
	if strings.TrimSpace(r.imagePath) == "" && strings.TrimSpace(imagePath) == "" {
		return errors.New("parameter image_path is empty")
	}

	imagePath = strings.TrimSpace(imagePath)

	if !path.IsAbs(imagePath) {
		abspath, err := filepath.Abs(imagePath)
		if err != nil {
			return err
		}

		imagePath = abspath
	}

	if len(imagePath) > 260 && runtime.GOOS == "windows" {
		imagePath = `\\?\` + imagePath
	}

	if r.imagePath == imagePath && r.exifTags != nil && len(r.exifTags) > 0 {
		return nil
	}

	fi, err := os.Stat(imagePath)
	if err != nil {
		return err
	}

	if fi.IsDir() {
		return errors.New("path is a directory")
	}

	r.imagePath = imagePath

	fileBytes, err := os.ReadFile(imagePath)
	if err != nil {
		return err
	}

	rawExif, err := exif.SearchAndExtractExif(fileBytes)
	if err != nil {
		return err
	}

	if len(rawExif) == 0 {
		return errors.New("no exif tags found")
	}

	tags, _, err := exif.GetFlatExifData(rawExif, nil)
	if err != nil {
		return err
	}

	r.exifTags = tags

	return nil
}

func (r *ExifReader) GetCaptureTime(imagePath string) (time.Time, error) {
	err := r.readExifTagsFromImage(imagePath)
	if err != nil {
		return time.Time{}, err
	}

	for _, tag := range r.exifTags {
		if tag.TagName == "DateTimeOriginal" {
			timeStr := tag.Value.(string)

			if r.location == nil {
				r.location, err = time.LoadLocation("Asia/Shanghai")
				if err != nil {
					r.location = time.FixedZone("CST", 8*60*60)
				}
			}

			shotTime, err := time.ParseInLocation("2006:01:02 15:04:05", timeStr, r.location)
			if err == nil {

				return shotTime, nil
			}
		}
	}

	return time.Time{}, errors.New("no capture time information in exif")
}

func (r *ExifReader) GetCaptureTimeString(imagePath string) (string, error) {
	captureTime, err := r.GetCaptureTime(imagePath)
	if err != nil {
		return "", err
	}

	return captureTime.Format("2006-01-02 15:04:05 MST"), nil
	//return captureTime.String(), nil
}

func (r *ExifReader) GetCaptureLocation(imagePath string) (float64, float64, float64, error) {
	err := r.readExifTagsFromImage(imagePath)
	if err != nil {
		return 0, 0, 0, err
	}

	gpsTags := map[string]interface{}{}

	for _, tag := range r.exifTags {
		switch tag.TagName {
		case "GPSLatitude":
			gpsTags["lat"] = tag.Value
		case "GPSLongitude":
			gpsTags["lon"] = tag.Value
		case "GPSLatitudeRef":
			gpsTags["lat_ref"] = tag.Value
		case "GPSLongitudeRef":
			gpsTags["lon_ref"] = tag.Value
		case "GPSAltitude":
			gpsTags["alt"] = tag.Value
		case "GPSAltitudeRef":
			gpsTags["alt_ref"] = tag.Value
		}
	}

	if len(gpsTags) == 0 {
		return 0, 0, 0, errors.New("no capture location information is found in exif")
	} else if len(gpsTags) == 6 {
		lat := gpsTags["lat"].([]exifcommon.Rational)
		lon := gpsTags["lon"].([]exifcommon.Rational)
		alt := gpsTags["alt"].([]exifcommon.Rational)

		latDec := float64(lat[0].Numerator)/float64(lat[0].Denominator) + float64(lat[1].Numerator)/float64(lat[1].Denominator)/60 + float64(lat[2].Numerator)/float64(lat[2].Denominator)/3600
		lonDec := float64(lon[0].Numerator)/float64(lon[0].Denominator) + float64(lon[1].Numerator)/float64(lon[1].Denominator)/60 + float64(lon[2].Numerator)/float64(lon[2].Denominator)/3600

		println("alt: %v", alt)

		if gpsTags["lat_ref"] == "S" {
			latDec = -latDec
		}

		if gpsTags["lon_ref"] == "W" {
			lonDec = -lonDec
		}

		return latDec, lonDec, latDec, nil
	} else {
		return 0, 0, 0, errors.New("capture location information in exif is invalid")
	}
}

func main() {
	imagePath := "./photos/MB_10X-DSC_1551.JPG"
	reader := NewExifReader(imagePath)

	captureTime, err := reader.GetCaptureTime(imagePath)
	if err != nil {
		println(err.Error())
	} else {
		println("capture time as UTC: ", captureTime.UTC().String())
		println("Capture time as local: ", captureTime.Local().String())
	}

	timeString, err := reader.GetCaptureTimeString(imagePath)
	if err != nil {
		println(err.Error())
	} else {
		println("capture time string: ", timeString)
	}

	lat, lon, alt, err := reader.GetCaptureLocation(imagePath)
	if err != nil {
		println(err.Error())
	} else {
		println("latitude: ", lat)
		println("longitude: ", lon)
		println("altitude: ", alt)
	}
}
